﻿// ===============================================================================
// XCI.NET 西安交通信息WinForm开发框架 2018 版权所有
// ===============================================================================
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using XCI.Core;

namespace XCI.Helper
{
    /// <summary>
    /// 字符串加密解密类库
    /// </summary>
    public static class EncryptHelper
    {
        /// <summary>
        /// 对称加密密钥
        /// </summary>
        public const string SecretKey = "!@#$TFFhanjianpdfdjk)(*&88330921NVJ878";

        /// <summary>
        /// DES对称算法加密字符串
        /// </summary>
        /// <param name="encryptString">待加密的字符串</param>
        /// <param name="secretKey">8位密钥</param>
        /// <param name="encoding">字符编码</param>
        /// <returns>返回密文</returns>
        public static string DESEncrypt(string encryptString, string secretKey, Encoding encoding)
        {
            if (secretKey.Length != 8) throw new ArgumentException("密钥必须是8个字符");
            DESCryptoServiceProvider dcsp = new DESCryptoServiceProvider();
            byte[] rgbKey = encoding.GetBytes(secretKey);
            byte[] inputByteArray = encoding.GetBytes(encryptString);

            MemoryStream mStream = new MemoryStream();
            CryptoStream cStream = new CryptoStream(mStream, dcsp.CreateEncryptor(rgbKey, new byte[rgbKey.Length]),
                CryptoStreamMode.Write);
            cStream.Write(inputByteArray, 0, inputByteArray.Length);
            cStream.FlushFinalBlock();
            return Convert.ToBase64String(mStream.ToArray());
        }

        /// <summary>
        /// DES对称算法解密字符串
        /// </summary>
        /// <param name="decryptString">待解密的字符串</param>
        /// <param name="secretKey">8位密钥</param>
        /// <param name="encoding">字符编码</param>
        /// <returns>返回原文</returns>
        public static string DESDecrypt(string decryptString, string secretKey, Encoding encoding)
        {
            if (secretKey.Length != 8) throw new ArgumentException("密钥必须是8个字符");
            byte[] rgbKey = encoding.GetBytes(secretKey);
            byte[] inputByteArray = Convert.FromBase64String(decryptString);
            DESCryptoServiceProvider dcsp = new DESCryptoServiceProvider();
            MemoryStream mStream = new MemoryStream();
            CryptoStream cStream = new CryptoStream(mStream, dcsp.CreateDecryptor(rgbKey, new byte[rgbKey.Length]), CryptoStreamMode.Write);
            cStream.Write(inputByteArray, 0, inputByteArray.Length);
            cStream.FlushFinalBlock();
            return encoding.GetString(mStream.ToArray());
        }

        /// <summary>
        /// MD5加密字符串(32位小写)
        /// </summary>
        /// <param name="encryptString">待加密的字符串</param>
        /// <returns>返回密文</returns>
        public static string Md5Encrypt(string encryptString)
        {
            StringBuilder sBuilder = new StringBuilder();
            using (MD5 md5Hash = MD5.Create())
            {
                byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(encryptString));

                for (int i = 0; i < data.Length; i++)
                {
                    sBuilder.Append(data[i].ToString("x2"));
                }
            }
            return sBuilder.ToString();
        }

        /// <summary>
        /// MD5加密字符串(16位小写)
        /// </summary>
        /// <param name="encryptString">待加密的字符串</param>
        /// <returns>返回密文</returns>
        public static string Md5Encrypt16(string encryptString)
        {
            MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
            string t2 = BitConverter.ToString(md5.ComputeHash(Encoding.UTF8.GetBytes(encryptString)), 4, 8);
            t2 = t2.Replace("-", "");
            t2 = t2.ToLower();
            return t2;
        }

        /// <summary>
        /// 创建RSA非对称算法密钥对
        /// </summary>
        /// <returns>返回密钥对封装对象</returns>
        public static RSAKey CreateRSASecretKey()
        {
            CspParameters cpSend = new CspParameters();
            cpSend.KeyContainerName = "XCI_Container_lvyanyang_2016";
            RSACryptoServiceProvider rsaSend = new RSACryptoServiceProvider(cpSend);
            var publicKey = rsaSend.ToXmlString(false);
            var privateKey = rsaSend.ToXmlString(true);
            return new RSAKey(publicKey, privateKey);
        }

        /// <summary>
        /// RSA非对称算法加密字符串
        /// </summary>
        /// <param name="encryptString">待加密的字符串</param>
        /// <param name="publicKey">公钥</param>
        /// <returns>返回密文</returns>
        public static string RSAEncrypt(string encryptString, string publicKey)
        {
            RSACryptoServiceProvider rsa = CreateRsaProviderFromPublicKey(publicKey);

            byte[] byteContent = Encoding.UTF8.GetBytes(encryptString);
            //if (byteContent.Length > 117) throw new ArgumentException("待处理字符串超出最大长度限制!");
            var cipherbytes = rsa.Encrypt(byteContent, false);
            return Convert.ToBase64String(cipherbytes);
        }

        /// <summary>
        /// RSA非对称算法解密字符串
        /// </summary>
        /// <param name="decryptString">待解密的字符串</param>
        /// <param name="privateKey">私钥</param>
        /// <returns>返回原文</returns>
        public static string RSADecrypt(string decryptString, string privateKey)
        {
            RSACryptoServiceProvider rsa = CreateRsaProviderFromPrivateKey(privateKey);

            byte[] byteContent = Convert.FromBase64String(decryptString);
            //if (byteContent.Length > 128) throw new ArgumentException("待处理字符串超出最大长度限制!");
            var cipherbytes = rsa.Decrypt(byteContent, false);
            return Encoding.UTF8.GetString(cipherbytes);
        }

        private static RSACryptoServiceProvider CreateRsaProviderFromPrivateKey(string privateKey)
        {
            var privateKeyBits = System.Convert.FromBase64String(privateKey);

            var RSA = new RSACryptoServiceProvider();
            var RSAparams = new RSAParameters();

            using (BinaryReader binr = new BinaryReader(new MemoryStream(privateKeyBits)))
            {
                byte bt = 0;
                ushort twobytes = 0;
                twobytes = binr.ReadUInt16();
                if (twobytes == 0x8130)
                    binr.ReadByte();
                else if (twobytes == 0x8230)
                    binr.ReadInt16();
                else
                    throw new Exception("Unexpected value read binr.ReadUInt16()");

                twobytes = binr.ReadUInt16();
                if (twobytes != 0x0102)
                    throw new Exception("Unexpected version");

                bt = binr.ReadByte();
                if (bt != 0x00)
                    throw new Exception("Unexpected value read binr.ReadByte()");

                RSAparams.Modulus = binr.ReadBytes(GetIntegerSize(binr));
                RSAparams.Exponent = binr.ReadBytes(GetIntegerSize(binr));
                RSAparams.D = binr.ReadBytes(GetIntegerSize(binr));
                RSAparams.P = binr.ReadBytes(GetIntegerSize(binr));
                RSAparams.Q = binr.ReadBytes(GetIntegerSize(binr));
                RSAparams.DP = binr.ReadBytes(GetIntegerSize(binr));
                RSAparams.DQ = binr.ReadBytes(GetIntegerSize(binr));
                RSAparams.InverseQ = binr.ReadBytes(GetIntegerSize(binr));
            }

            RSA.ImportParameters(RSAparams);
            return RSA;
        }

        private static int GetIntegerSize(BinaryReader binr)
        {
            byte bt = 0;
            byte lowbyte = 0x00;
            byte highbyte = 0x00;
            int count = 0;
            bt = binr.ReadByte();
            if (bt != 0x02)
                return 0;
            bt = binr.ReadByte();

            if (bt == 0x81)
                count = binr.ReadByte();
            else
                if (bt == 0x82)
            {
                highbyte = binr.ReadByte();
                lowbyte = binr.ReadByte();
                byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
                count = BitConverter.ToInt32(modint, 0);
            }
            else
            {
                count = bt;
            }

            while (binr.ReadByte() == 0x00)
            {
                count -= 1;
            }
            binr.BaseStream.Seek(-1, SeekOrigin.Current);
            return count;
        }

        private static RSACryptoServiceProvider CreateRsaProviderFromPublicKey(string publicKeyString)
        {
            // encoded OID sequence for  PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
            byte[] SeqOID = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
            byte[] x509key;
            byte[] seq = new byte[15];
            int x509size;

            x509key = Convert.FromBase64String(publicKeyString);
            x509size = x509key.Length;

            // ---------  Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob  ------
            using (MemoryStream mem = new MemoryStream(x509key))
            {
                using (BinaryReader binr = new BinaryReader(mem))  //wrap Memory Stream with BinaryReader for easy reading
                {
                    byte bt = 0;
                    ushort twobytes = 0;

                    twobytes = binr.ReadUInt16();
                    if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
                        binr.ReadByte();    //advance 1 byte
                    else if (twobytes == 0x8230)
                        binr.ReadInt16();   //advance 2 bytes
                    else
                        return null;

                    seq = binr.ReadBytes(15);       //read the Sequence OID
                    if (!CompareBytearrays(seq, SeqOID))    //make sure Sequence for OID is correct
                        return null;

                    twobytes = binr.ReadUInt16();
                    if (twobytes == 0x8103) //data read as little endian order (actual data order for Bit String is 03 81)
                        binr.ReadByte();    //advance 1 byte
                    else if (twobytes == 0x8203)
                        binr.ReadInt16();   //advance 2 bytes
                    else
                        return null;

                    bt = binr.ReadByte();
                    if (bt != 0x00)     //expect null byte next
                        return null;

                    twobytes = binr.ReadUInt16();
                    if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
                        binr.ReadByte();    //advance 1 byte
                    else if (twobytes == 0x8230)
                        binr.ReadInt16();   //advance 2 bytes
                    else
                        return null;

                    twobytes = binr.ReadUInt16();
                    byte lowbyte = 0x00;
                    byte highbyte = 0x00;

                    if (twobytes == 0x8102) //data read as little endian order (actual data order for Integer is 02 81)
                        lowbyte = binr.ReadByte();  // read next bytes which is bytes in modulus
                    else if (twobytes == 0x8202)
                    {
                        highbyte = binr.ReadByte(); //advance 2 bytes
                        lowbyte = binr.ReadByte();
                    }
                    else
                        return null;
                    byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };   //reverse byte order since asn.1 key uses big endian order
                    int modsize = BitConverter.ToInt32(modint, 0);

                    int firstbyte = binr.PeekChar();
                    if (firstbyte == 0x00)
                    {   //if first byte (highest order) of modulus is zero, don't include it
                        binr.ReadByte();    //skip this null byte
                        modsize -= 1;   //reduce modulus buffer size by 1
                    }

                    byte[] modulus = binr.ReadBytes(modsize);   //read the modulus bytes

                    if (binr.ReadByte() != 0x02)            //expect an Integer for the exponent data
                        return null;
                    int expbytes = (int)binr.ReadByte();        // should only need one byte for actual exponent data (for all useful values)
                    byte[] exponent = binr.ReadBytes(expbytes);

                    // ------- create RSACryptoServiceProvider instance and initialize with public key -----
                    RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
                    RSAParameters RSAKeyInfo = new RSAParameters();
                    RSAKeyInfo.Modulus = modulus;
                    RSAKeyInfo.Exponent = exponent;
                    RSA.ImportParameters(RSAKeyInfo);

                    return RSA;
                }

            }
        }

        private static bool CompareBytearrays(byte[] a, byte[] b)
        {
            if (a.Length != b.Length)
                return false;
            int i = 0;
            foreach (byte c in a)
            {
                if (c != b[i])
                    return false;
                i++;
            }
            return true;
        }

        /// <summary>
        /// 字符串数字签名
        /// </summary>
        /// <param name="originalString">待签名字符串</param>
        /// <param name="privateKey">私钥</param>
        /// <returns>返回数字签名</returns>
        public static string DigitalSignature(string originalString, string privateKey)
        {
            byte[] DataToSign = Encoding.UTF8.GetBytes(originalString);

            RSACryptoServiceProvider RSAalg = new RSACryptoServiceProvider();
            RSAalg.FromXmlString(privateKey);
            byte[] signedData = RSAalg.SignData(DataToSign, new SHA1CryptoServiceProvider());
            return Convert.ToBase64String(signedData);
        }

        /// <summary>
        /// 验证数字签名
        /// </summary>
        /// <param name="originalString">原始字符串</param>
        /// <param name="signedString">数字签名字符串</param>
        /// <param name="publicKey">公钥</param>
        /// <returns>如果验证透过返回true</returns>
        public static bool VerifyDigitalSignature(string originalString, string signedString, string publicKey)
        {
            byte[] signedData = Convert.FromBase64String(signedString);
            byte[] dataToVerify = Encoding.UTF8.GetBytes(originalString);

            RSACryptoServiceProvider RSAalg = new RSACryptoServiceProvider();
            RSAalg.FromXmlString(publicKey);
            return RSAalg.VerifyData(dataToVerify, new SHA1CryptoServiceProvider(), signedData);
        }

        /// <summary>
        /// 对称加密
        /// </summary>
        /// <param name="plainText">明文</param>
        /// <param name="secretKey">密钥</param>
        /// <returns>密文</returns>
        public static string SymmetricEncrypt(string plainText, string secretKey)
        {
            Guard.NotNullOrWhiteSpace(secretKey, nameof(secretKey));
            SymmetricAlgorithm algorithm = new TripleDESCryptoServiceProvider();
            MD5CryptoServiceProvider hashMd5 = new MD5CryptoServiceProvider();
            algorithm.Key = hashMd5.ComputeHash(Encoding.UTF8.GetBytes(secretKey));
            algorithm.Mode = CipherMode.ECB;
            ICryptoTransform transformer = algorithm.CreateEncryptor();
            byte[] buffer = Encoding.UTF8.GetBytes(plainText);
            return Convert.ToBase64String(transformer.TransformFinalBlock(buffer, 0, buffer.Length));
        }

        /// <summary>
        /// 对称解密
        /// </summary>
        /// <param name="encryptText">密文</param>
        /// <param name="secretKey">密钥</param>
        /// <returns>明文</returns>
        public static string SymmetricDecrypt(string encryptText, string secretKey)
        {
            Guard.NotNullOrWhiteSpace(secretKey, nameof(secretKey));
            if (string.IsNullOrEmpty(encryptText))
            {
                return string.Empty;
            }
            SymmetricAlgorithm algorithm = new TripleDESCryptoServiceProvider();
            MD5CryptoServiceProvider hashMd5 = new MD5CryptoServiceProvider();
            algorithm.Key = hashMd5.ComputeHash(Encoding.UTF8.GetBytes(secretKey));
            algorithm.Mode = CipherMode.ECB;
            ICryptoTransform transformer = algorithm.CreateDecryptor();
            byte[] buffer = Convert.FromBase64String(encryptText);
            return Encoding.UTF8.GetString(transformer.TransformFinalBlock(buffer, 0, buffer.Length));
        }
    }

    /// <summary>
    /// RSA密钥对
    /// </summary>
    public class RSAKey
    {
        /// <summary>
        /// RSA密钥对
        /// </summary>
        /// <param name="publicKey">公钥</param>
        /// <param name="privateKey">私钥</param>
        public RSAKey(string publicKey, string privateKey)
        {
            PublicKey = publicKey;
            PrivateKey = privateKey;
        }

        /// <summary>
        /// 公钥
        /// </summary>
        public string PublicKey { get; set; }

        /// <summary>
        /// 私钥
        /// </summary>
        public string PrivateKey { get; set; }
    }
}